﻿/* ***************************************************************************************
 * Linq To SystemSearch
 * 
 *
 * This is an example of how extend LinQ using IQueruable and IOrderedQuerable interfaces.
 * 
 * User this code as you want,  is free.
 * 
 * Develop by: Mario Rodriguez
 * email: marior666@hotmail.com
 * 
 ********************************************************************************************/


using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Linq;
using System.Linq.Expressions;

namespace System.Search.Linq
{
    /// <summary>
    /// Class use to executed and map a result dataset into unknow object T
    /// </summary>
    /// <typeparam name="T">Type of returned object by the IEnumerator</typeparam>
    [Serializable]
    internal class Query<T> : IEnumerable<T>, IDisposable
    {
        /// <summary>
        /// Mapping of columns and object properties
        /// </summary>
        public List<ColumnMappingInfo> _columnsMappingList = new List<ColumnMappingInfo>();

        #region Properties

        /// <summary>
        /// Return the las added column mapping
        /// </summary>
        public ColumnMappingInfo CurrentColumnMapping
        {

            get
            {
                if (_columnsMappingList.Count != 0)
                    return _columnsMappingList[_columnsMappingList.Count - 1];
                return null;
            }
        }
       
        /// <summary>
        /// Represent the remote Machine Name to run the SystemIndex query
        /// </summary>
        public string MachineName { get; set; }

        /// <summary>
        /// Select part of the SQL Query
        /// </summary>
        public string Projection { get; set; }

     
        /// <summary>
        /// Where part of the SQL Query
        /// </summary>
        public string Where { get; set; }

       
        /// <summary>
        /// Order by Part of the SQL Query
        /// </summary>
        public string Order { get; set; }

        /// <summary>
        ///  Grouping part of the SQL Query
        /// </summary>

        public string Grouping { get; set; }

        /// <summary>
        /// Max results to return. Take function.
        /// </summary>
        public int? MaxResults { get; set; }

        /// <summary>
        /// Start at n element. see Skip function
        /// </summary>
        public int? StarAtIndex { get; set; }

        
        /// <summary>
        /// Lambda expression to be executed dynamically to represent the function TakeWhile
        /// </summary>
        [NonSerialized]
        LambdaExpression takeWhile;

        /// <summary>
        /// Lambda expression to be executed dynamically to represent the function TakeWhile
        /// </summary>
        public LambdaExpression TakeWhile { get { return takeWhile; } set { takeWhile = value; } }

        /// <summary>
        /// Lambda expression to be executed dynamically to represent the function SkipWhile
        /// </summary>
        [NonSerialized]
        LambdaExpression skipWhile;

        /// <summary>
        /// Lambda expression to be executed dynamically to represent the function TakeWhile
        /// </summary>
        public LambdaExpression SkipWhile { get { return skipWhile; } set { skipWhile = value; } }
    
        /// <summary>
        /// Dataset field to store the result of the SQL Execution
        /// </summary>
        DataSet m_dataset = null;

        /// <summary>
        /// Readonly DataSet Property to store the result of the SQL Execution
        /// </summary>
        public DataSet DataSet { get { return m_dataset; } }

        
       // const string connectionString = "Provider=Search.CollatorDSO;Data Source=marior-sc;Initial Catalog=Search.CollatorDSO;Integrated Security=SSPI;User ID=SOUTHAMERICA\\mariorm;Password=Javier25.25";
        
        /// <summary>
        /// Return the IEnumerator for all object instantiate form m_dataSet
        /// </summary>
        public IEnumerator<T> Items { get { return getItems(); } }

        #endregion

       
        #region Internal Methods

        /// <summary>
        /// Add a ColumnMapping to the list of CM
        /// </summary>
        /// <returns>Return the last inserted column Mapping</returns>
        public ColumnMappingInfo AddColumnMapping()
        {
            ColumnMappingInfo cm = new ColumnMappingInfo();
            cm.ColumIndex = _columnsMappingList.Count;
            _columnsMappingList.Add(cm);
            return cm;
        }

        /// <summary>
        /// Build and execute the desktop search Query depending of  Projection, Where, OrderBy properties

        /// 
        /// </summary>
        /// <returns> Return a IEnumerator of Object T as a result of the Query execution using OLEDB</returns>
        private IEnumerator<T> getItems()
        {

        
            // construct the DS query to be executed
            string query = MakeSQLStatement();

            // Execute the query and get the corresponding DataSet
            ExecuteQueryDataSet(query);

            // Compile all lambda expression to be executed with all results objects
            Delegate takeWhileDelegate = TakeWhile != null ? TakeWhile.Compile() : null;
            Delegate skipWhileDelegate = SkipWhile != null ? SkipWhile.Compile() : null;
            

            
            if (m_dataset.Tables.Count == 1)
            {
                DataTable table = m_dataset.Tables[0];
                int i = 0;

                // Iterate for all elements of the data set using the StartIndex as the firs element
                // See the operator Skip
                for (int iterator = (StarAtIndex != null ? (int)StarAtIndex : 0);
                     iterator < table.Rows.Count;
                     iterator++)
                {
                    DataRow row = table.Rows[iterator];

                    // ask for the Maxresults to be returned
                    // Operator Take
                    if (MaxResults != null && MaxResults <= i)
                    {
                        break;

                    }

                    // Convert the dataset row to an object array
                    object[] arguments = row.ItemArray.ToArray<object>();

                    // parse all arguments using the columnmapping information to instatiate
                    // all needed objects
                    arguments = GenerateInvocationArguments(arguments, table.Columns);

                    // Create the object instance to be returned
                    T returnobject = (T)Activator.CreateInstance(typeof(T), arguments);

                    // if takeWhileDelegate then evaluate de function an break the execution
                    // if the result is true
                    if (takeWhileDelegate != null)
                    {
                        if (!(bool)takeWhileDelegate.DynamicInvoke(new object[] { returnobject }))
                            break;
                    }


                    // if skipWhileDelegate then evaluate de function an continue the execution
                    // if the result is true
                    if (skipWhileDelegate != null)
                    {
                        if ((bool)skipWhileDelegate.DynamicInvoke(new object[] { returnobject }))
                            continue;
                    }

                    i++;

                    // return the create object representing "select" method projection
                    yield return returnobject;


                }

            }

            // Clear the dataset
            m_dataset.Clear();

        }

        /// <summary>
        /// Execute OleDb Query using search provider for OleDb Search.CollatorDSO.
        /// </summary>
        /// <param name="query">DS SQL query to be executed</param>
        /// <returns>return a DataSet </returns>
        private DataSet ExecuteQueryDataSet(string query)
        {
            // Build the connection string
            OleDbConnectionStringBuilder builder = new OleDbConnectionStringBuilder();

            builder.Provider = "Search.CollatorDSO";
            builder.Add("Extended Properties", "Application=Windows");

            // create the OleDB connection and command to be executed
            OleDbConnection myOleDbConnection = new OleDbConnection(builder.ConnectionString);
            
            OleDbCommand myOleDbCommand = new OleDbCommand(query, myOleDbConnection);
           

            try
            {

                // Open the conection an fill the Dataset
                myOleDbConnection.Open();

                OleDbDataAdapter adapter = new OleDbDataAdapter(myOleDbCommand);
                m_dataset = new DataSet();
                adapter.Fill(m_dataset);
                


            }
            catch (Exception e)
            {
                //TODO: throw and Runtime Exception
                Console.WriteLine(e.Message);
            }
            finally
            {
                // close all open resources
               
                myOleDbConnection.Close();

            }
            
            return m_dataset;
        }

        /// <summary>
        /// Concatenate Projection, Where and Order properties to conform a well 
        /// formed SQL command text
        /// </summary>
        /// <returns></returns>
        internal  string MakeSQLStatement()
        {
            

            string topPart = MaxResults != null && StarAtIndex == null ? " TOP " +MaxResults + " " : "";
            string machineNamePart = (MachineName != null ? MachineName + "." : "");

            string query = "SELECT " + topPart + Projection +
                            " FROM " + machineNamePart + "SystemIndex";
            if (Where != null)
                query += " WHERE " + Where;

            if (Order != null)
                query += " ORDER BY " + Order;
            return query;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arguments"></param>
        /// <param name="columns"></param>
        /// <returns></returns>
        private object[] GenerateInvocationArguments(object[] arguments, DataColumnCollection columns)
        {
            List<object> result = new List<object>();
            object currentPropertyInstance = null;
            Type currentPropertyType = null;


            foreach (ColumnMappingInfo mi in _columnsMappingList)
            {

                if (mi.HasToConvert)
                {
                    if (!mi.ObjectType.Equals(currentPropertyType))
                    {
                        currentPropertyType = mi.ObjectType;
                        currentPropertyInstance = Activator.CreateInstance(mi.ObjectType);
                        result.Add(currentPropertyInstance);
                    }
                    object arg = CheckArguments(arguments[mi.ColumIndex], mi.PropertyInfo.PropertyType);
                    mi.PropertyInfo.SetValue(currentPropertyInstance, arg, null);


                }
                else
                {

                    result.Add(CheckArguments(arguments[mi.ColumIndex], mi.PropertyInfo.PropertyType));
                }

            }

            return result.ToArray();
        }

        /// <summary>
        /// Conver all needed arguments to the object constructor parameter type
        /// </summary>
        /// <param name="arguments"></param>
        /// <param name="PropertyType"></param>
        /// <returns></returns>
        private object CheckArguments(object arguments, Type PropertyType)
        {


            if (!arguments.GetType().Equals(PropertyType))
            {
                try
                {
                    arguments = (object)Convert.ChangeType(arguments, PropertyType);
                }
                catch (InvalidCastException )
                {
                    if (!PropertyType.IsArray)
                    {
                        arguments = Activator.CreateInstance(PropertyType);
                    }
                    else
                    {
                        arguments = null;// Activator.CreateInstance(PropertyType, null);
                    }
                }
            }


            return arguments;
        }

        #endregion


        #region IEnumerable<T> Members
        /// <summary>
        /// See also GetEnumerator of IEnumerable<T> Interface
        /// </summary>
        /// <returns></returns>
        public IEnumerator<T> GetEnumerator()
        {
            return Items;
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// See also GetEnumerator of interface IEnumerable
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Items;
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// See also Dispose method of IDisposable interface
        /// </summary>
        public void Dispose()
        {
            m_dataset.Clear();
            m_dataset.Dispose();
        }

        #endregion


      
    }
}
